package com.na.game.engine.opengl;

public class GLBufferManager {

	private static int[] levels = new int[]{8, 16, 32, 64, 128, 256, 512, 1024, 2048};
	
	private static GLBuffer[][] shortBuffers = new GLBuffer[levels.length][10];
	private static GLBuffer[][] floatBuffers = new GLBuffer[levels.length][10];
	private static int[] shortBufferSizes = new int[levels.length];
	private static int[] floatBufferSizes = new int[levels.length];
	
	public static GLShortBuffer allocateShortBuffer(int capacity) {
		int index = findIndex(capacity);
		if (index < 0) {
			return new GLShortBuffer(index, capacity);
		}
		int remain = shortBufferSizes[index];
		if (remain > 0) {
			return (GLShortBuffer) removeBuffer(index, true);
		}
		return new GLShortBuffer(index, levels[index]);
	}
	
	public static GLFloatBuffer allocateFloatBuffer(int capacity) {
		int index = findIndex(capacity);
		if (index < 0) {
			return new GLFloatBuffer(index, capacity);
		}
		int remain = floatBufferSizes[index];
		if (remain > 0) {
			return (GLFloatBuffer) removeBuffer(index, false);
		}
		return new GLFloatBuffer(index, levels[index]);
	}
	
	private static GLBuffer removeBuffer(int index, boolean isShortBuffer) {
		if (isShortBuffer) {
			return removeBuffer(index, shortBuffers[index], shortBufferSizes);
		}
		return removeBuffer(index, floatBuffers[index], floatBufferSizes);
	}
	
	private static GLBuffer removeBuffer(int index, GLBuffer[] buffers, int[] sizes) {
		int last = --sizes[index];
		GLBuffer buffer = buffers[last];
		buffer.setRecyclable(true);
		buffers[last] = null;
		return buffer;
	}
	
	protected static void release(GLBuffer buffer) {
		int index = buffer.index;
		if (index < 0 || !buffer.isRecyclable())
			return;
		
		buffer.reset();
		
		GLBuffer[][] buffers;
		int[] sizes;
		if (buffer instanceof GLShortBuffer) {
			buffers = shortBuffers;
			sizes = shortBufferSizes;
		} else {
			buffers = floatBuffers;
			sizes = floatBufferSizes;
		}
		
		GLBuffer[] newBuffer = release(buffers[index], sizes, buffer);
		if (newBuffer != null) {
			buffers[index] = newBuffer;
		}
	}
	
	private static GLBuffer[] release(GLBuffer[] buffers, int[] sizes, GLBuffer target) {
		int index = target.index;
		int size = sizes[index]++;
		if (buffers.length > size) {
			buffers[size] = target;
			return null;
		}
		
		int newCapacity = buffers.length * 3 / 2;
		GLBuffer[] dst = new GLBuffer[newCapacity];
		System.arraycopy(buffers, 0, dst, 0, buffers.length);
		dst[size] = target;
		return dst; // new
	}
	
	public static void destroy() {
		destroy(shortBuffers);
		destroy(floatBuffers);
		destroy(shortBufferSizes);
		destroy(floatBufferSizes);
	}
	
	private static void destroy(GLBuffer[][] buffers) {
		for (int i = buffers.length - 1; i >= 0; i--) {
			GLBuffer[] bfrs = buffers[i];
			for (int j = bfrs.length - 1; j >= 0; j--) {
				bfrs[i] = null;
			}
		}
	}
	
	private static void destroy(int[] sizes) {
		for (int i = sizes.length - 1; i >= 0; i--) {
			sizes[i] = 0;
		}
	}
	
	private static int findIndex(int capacity) {
		int[] lvls = levels;
		int from = 0;
		int to = lvls.length - 1;
		if (capacity < lvls[0]) {
			return 0;
		}
		if (capacity > lvls[to]) {
			return -1;
		}
		int index = -1;
		while (from <= to) {
			index = from + ((to - from) >> 1);
			if (capacity == lvls[index]) {
				return index;
			}
			if (capacity < lvls[index]) {
				to = index - 1;
			} else {
				from = index + 1;
			}
		}
		if (capacity > lvls[index]) {
			return index + 1;
		}
		return index;
	}
	
	
}
